package com.b1.taskmanagerserver.connect;

import com.b1.taskmanagerserver.api.TaskList;
import com.b1.taskmanagerserver.api.ContactList;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;

/**
 * Класс сервера
 * обеспечивает работу сервера.
 * отвечает за подключение клиентов и взаимодействия с ними
 * прием отправку данны: списка контактов, списка задач
 * включает в себя слушателя наступления задачи
 */
public class Server
{
    private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(Server.class);
    
    private HashMap <Client,Boolean> clientList = new HashMap<Client,Boolean>(); // список подключенных клиентов, булевое значение является определением достоверности версии данных у клиента
    private HashMap <Client,String> clientListForTask = new HashMap<Client,String>(); // список подключенных клиентов и сообщений для них 
    
    private int port = 4534;            // порт сервера по умолчанию
    private TaskList taskList;          // список задач
    private ContactList contactList;    // список контактов
    private ViewDates listener;         // слушатель задач
    
    /**
     * Класс отвечающий за индетификацию клиентов на сервере
     */
    private class Client
    {
        private Server clientServer;    //сервер
        private Socket clientSocket;    // сокет клиента
        
        /**
         * получение сервера
         * @return Server
         */
        public Server getServer() {
            return clientServer;
        }
        
        /**
         * присвоение сервера
         * @param server 
         */
        public void setServer(Server server) {
            this.clientServer = server;
        }
        
        /**
         * получение клиентского сокета
         * @return Socet 
         */
        public Socket getSocket() {
            return clientSocket;
        }

        /**
         * присвоение клиентского сокета
         * @param socket
         */
        public void setSocket(Socket socket) {
            this.clientSocket = socket;
        }
        
        /**
         * конструктор класса
         * @param socket
         * @param server 
         */
        public Client(Socket socket,Server server) {
            setServer(server);
            setSocket(socket);
        }
    }
    
    /**
     * Конструктор класса Server
     * ждет подключения клиентов, для каждого клиента создается новый поток
     * получает taskList и contactList
     * запускает поток слушателя
     * заполняет списки клиентов
     * @param port
     * @throws IOException 
     */
    public Server(int port) throws IOException 
    {
        ServerSocket serverSocket = new ServerSocket(port);
        taskList = new TaskList();
        contactList = new ContactList();
        listener = new ViewDates();
        listener.start();
        log.info("Server: started");
        while (true) 
        {
            Socket socket = serverSocket.accept();
            Client newClient = new Client(socket, this);
            taskList = new TaskList();
            contactList = new ContactList();
            log.info("new client: "+socket.toString()+" connected");
            clientList.put(newClient, true);
            clientListForTask.put(newClient,null);
           new Server.Start(socket).start();
        }
    }
    
    /**
     * Класс поток для каждого клинта
     * обеспечивает взаимодействие клиента и сервера
     * является Daemon потоком
     */
    private class Start extends Thread
    {
        private Socket socket;      //сокет подключенного клиента

        /**
         * конструктор класса
         * устранавливает что поток Daemon
         * получает значение сокета
         * @param socket 
         */
        public Start(Socket socket ) 
        {
            this.socket = socket;
            this.setDaemon(true);
        }
        
        /**
         * потоковый метод
         * отвечает за прием запросов от клиента
         * и отправки ему данных
         */
        public void run() 
        {
            ObjectOutputStream out;
            ObjectInputStream in;
            try 
            {
                out = new ObjectOutputStream(socket.getOutputStream());
                in = new ObjectInputStream(socket.getInputStream());
                while (true) 
                {
                    Object buf = in.readObject();
                    if (buf.equals(0)) // запрос на получение taskList
                    {
                        out.writeObject("ok");
                        out.flush();
                        out.writeObject(taskList.listToString());
                        out.flush();
                        continue;
                    }
                    if (buf.equals(1)) // запрос на получение contactList
                    {
                        out.writeObject("ok");
                        out.flush();
                        out.writeObject(contactList.listToString());
                        out.flush();
                        continue;
                    }
                    if (buf.equals(2)) // запрос на сохранение TaskList 
                    {
                        saveTaskList((String[][])in.readObject());
                        continue;
                    }
                    if (buf.equals(3)) //запрос на сохранение contactList
                    {
                        saveContactList((String[][])in.readObject());
                        continue;
                    }
                    if(buf.equals(4)) // запрос на проверку есть ли новые данные
                    {
                        for (Client element : clientList.keySet()) 
                        {
                            if (element.getSocket().equals(socket))
                            {
                                if(!clientList.get(element))
                                {
                                   out.writeObject("new");
                                   out.writeObject(taskList.listToString());
                                   out.writeObject(contactList.listToString());
                                   clientList.put(element,true);
                                }
                                else out.writeObject("ok");
                            }   
                        }
                        continue;
                    }
                    if(buf.equals(5)) // запрос наступило ли какое-либо событие
                    {
                        for (Client element : clientListForTask.keySet()) 
                        {
                            if (element.getSocket().equals(socket))
                            {
                                if(clientListForTask.get(element)!=null)
                                {
                                    out.writeObject("ok");
                                    out.writeObject(clientListForTask.get(element));
                                    clientListForTask.put(element,null);
                                }
                                else out.writeObject("no");
                            }
                        }
                        continue;
                    }
                    else // обработка неизвестной команыд от клиента
                    {
                        log.info("unknown command "+buf.toString());
                        out.writeObject("uc");
                        out.flush();
                        continue;
                    }   
                }
            } 
            catch (SAXException ex) 
            {
                log.error(Server.class.getName(), ex);
            } 
            catch (ParserConfigurationException ex) 
            {
                log.error(Server.class.getName(), ex);
            }
            catch (ClassNotFoundException ex) 
            {  
                log.error(Server.class.getName(), ex);
            }
            catch (IOException ex) 
            {
                log.warn(Server.class.getName()+"\nClient Disconnected", ex);
            } 
        }
    }
    
    /**
     * метод измененич логической переменной в списке контактов
     * необходимо для того что бы клиент знал нужно ли ему получать новые данные
     * после выполнения метода всем подключенным клиентам необходимо обновить данные
     */
    private void change()
    {
        for (Client element : clientList.keySet())
        {
            clientList.put(element,false);
        }
    }
    /**
     * сохранение TaskList
     * перезапуск слушателя
     * @param newTaskList
     * @throws IOException
     * @throws SAXException
     * @throws ParserConfigurationException 
     */
    private void saveTaskList(String [][] newTaskList) throws IOException, SAXException, ParserConfigurationException
    {
        com.b1.taskmanagerserver.parser.TaskXml taskXml = new com.b1.taskmanagerserver.parser.TaskXml();
        taskXml.writeXml(newTaskList);
        taskList = new TaskList();
        listener.stop();
        listener = new ViewDates();
        listener.start();
        change();
        log.info("Save tasklist");
    }
    
    /**
     * сохранение ContactList
     * @param newContactList
     * @throws IOException
     * @throws SAXException
     * @throws ParserConfigurationException 
     */
    private void saveContactList(String [][] newContactList) throws IOException, SAXException, ParserConfigurationException
    {
        com.b1.taskmanagerserver.parser.ContactXml contactXml = new com.b1.taskmanagerserver.parser.ContactXml();
        contactXml.writeXml(newContactList);
        contactList = new ContactList();
        log.info("Save ContactList");
        change();
    }
    
    
    /**
     * Класс слушателя
     * отвечает за оповещение клиентов о наступлении собития
     * является Daemon потоком
     */
    public class ViewDates extends Thread
    {
        private HashMap<Date, String> taskMap;  // куча заголовков звдач и дат их наступления 
        private java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("HH:mm dd-MM-yyyy"); // формат хранения дат
        
        /**
         * конструктор класса
         * инициализирует кучу
         * устанавливает что поток Daemon
         */
        public ViewDates() 
        {
            taskMap = new HashMap<Date, String>();
            this.setDaemon(true);
        }
        
        /**
         * метод добавления сообщения(наступившей задачи)
         * для всех подключенных клиентов
         * @param message 
         */
        private void setMessage(String message) 
        {
            java.util.Set<Client> setClient = clientListForTask.keySet();
            Client[] arrayClient = (Client[]) setClient.toArray(new Client[setClient.size()]);
            int size = clientListForTask.size();
            for (int i = 0; i < size; i++)
                clientListForTask.put(arrayClient[i], message);
        }
        
        /**
         * потоковый метод
         * обеспечивает проверку времени наступления задачи и текущего времени системы
         * как только наступает время события добовляет сообщение для клиентов о данном событии
         */
        public void run() 
        {
            log.info("Start Listener");
            try 
            {
                int size = taskList.getTaskList().size();
                for (int i = 0; i < size; i++)
                    taskMap.put(sdf.parse(taskList.listToString()[i][1]), taskList.listToString()[i][0]);
                java.util.Set<Date> taskTimeSet = taskMap.keySet();
                Date[] taskTime = (Date[]) taskTimeSet.toArray(new Date[taskTimeSet.size()]);
                Calendar cal;
                while (true) 
                {
                    if(clientListForTask.isEmpty())continue;
                    cal = Calendar.getInstance();
                    cal.set(Calendar.MILLISECOND, 0);
                    cal.set(Calendar.SECOND, 0);
                    Date nowTime = cal.getTime();
                    size = taskTime.length;
                    for (int i = 0; i < size; i++) 
                    {
                        if (nowTime.compareTo(taskTime[i]) == 0) 
                        {
                            log.info("Coming Task" + taskMap.get(taskTime[i]).toString());
                            setMessage(taskList.showTask(Integer.parseInt(taskMap.get(taskTime[i]))));
                            taskTime[i] = sdf.parse("00:00 1-01-1970");
                            break;
                        }
                    }
                }
            } 
            catch (ParseException ex) 
            {
                log.error(Server.class.getName(), ex);
            }
        }
    }
}